package com.ihr360.rest.webmvc.json;

import com.ihr360.rest.core.Ihr360EnumTranslationConfiguration;
import org.springframework.context.NoSuchMessageException;
import org.springframework.context.support.MessageSourceAccessor;
import org.springframework.util.Assert;
import org.springframework.util.StringUtils;

import java.util.ArrayList;
import java.util.List;
import java.util.Locale;

/**
 * Configuration to tweak enum serialization.
 */
public class Ihr360EnumTranslator implements Ihr360EnumTranslationConfiguration {
    private final MessageSourceAccessor messageSourceAccessor;

    private boolean enableDefaultTranslation;
    private boolean parseEnumNameAsFallback;

    /**
     * Creates a new {@link Ihr360EnumTranslator} using the given {@link MessageSourceAccessor}.
     *
     * @param messageSourceAccessor must not be {@literal null}.
     */
    public Ihr360EnumTranslator(MessageSourceAccessor messageSourceAccessor) {

        Assert.notNull(messageSourceAccessor, "MessageSourceAccessor must not be null!");

        this.messageSourceAccessor = messageSourceAccessor;
        this.enableDefaultTranslation = true;
        this.parseEnumNameAsFallback = true;
    }

    /*
     * (non-Javadoc)
     * @see org.springframework.data.rest.core.config.EnumTranslationConfiguration#setEnableDefaultTranslation(boolean)
     */
    @Override
    public void setEnableDefaultTranslation(boolean enableDefaultTranslation) {
        this.enableDefaultTranslation = enableDefaultTranslation;
    }

    /*
     * (non-Javadoc)
     * @see org.springframework.data.rest.core.config.EnumTranslationConfiguration#setParseEnumNameAsFallback(boolean)
     */
    @Override
    public void setParseEnumNameAsFallback(boolean parseEnumNameAsFallback) {
        this.parseEnumNameAsFallback = parseEnumNameAsFallback;
    }

    /**
     * Resolves the given enum value into a {@link String} consulting the configured {@link MessageSourceAccessor}
     * potentially falling back to the default translation if configured. Returning the plain enum name if no resolution
     * applies.
     *
     * @param value must not be {@literal null}.
     * @return
     */
    public String asText(Enum<?> value) {

        Assert.notNull(value, "Enum value must not be null!");

        String code = String.format("%s.%s", value.getDeclaringClass().getName(), value.name());

        try {
            return messageSourceAccessor.getMessage(code);
        } catch (NoSuchMessageException o_O) {
            return enableDefaultTranslation ? toDefault(value) : value.name();
        }
    }

    public List<String> getValues(Class<? extends Enum<?>> type) {

        List<String> result = new ArrayList<String>();

        for (Enum<?> value : type.getEnumConstants()) {
            result.add(asText(value));
        }

        return result;
    }

    /**
     * Parses the given source text into the corresponding enum value using the configured {@link MessageSourceAccessor}
     * potentially falling back to the default translation or the plain enum name if configured.
     *
     * @param type must not be {@literal null}.
     * @param text can be {@literal null}
     * @return the resolved enum or {@literal null} if the resolution failed.
     */
    public <T extends Enum<?>> T fromText(Class<T> type, String text) {

        if (!StringUtils.hasText(text)) {
            return null;
        }

        Assert.notNull(type, "Enum type must not be null!");

        T value = resolveEnum(type, text, true);

        if (value != null) {
            return value;
        }

        value = fromDefault(type, text);

        // Only parse default translation if no explicit translation is available
        if (value != null && enableDefaultTranslation && asText(value).equals(text)) {
            return value;
        }

        return parseEnumNameAsFallback ? resolveEnum(type, text, false) : null;
    }

    /**
     * Resolves the given {@link String} text into an enum value of the given type potentially trying to resolve it
     * through the configured {@link MessageSourceAccessor}.
     *
     * @param type must not be {@literal null}.
     * @param text must not be {@literal null} or empty.
     * @param resolve whether to resolve the source {@link String} through the message source.
     * @return
     */
    @SuppressWarnings("unchecked")
    private <T extends Enum<?>> T resolveEnum(Class<T> type, String text, boolean resolve) {

        for (Enum<?> value : type.getEnumConstants()) {

            String resolved = resolve ? asText(value) : value.name();

            if (resolved != null && resolved.equals(text)) {
                return (T) value;
            }
        }

        return null;
    }

    /**
     * Renders a default translation for the given enum (capitalized, lower case, underscores replaced by spaces).
     *
     * @param value must not be {@literal null}.
     * @return
     */
    private String toDefault(Enum<?> value) {
        return StringUtils.capitalize(value.name().toLowerCase(Locale.US).replaceAll("_", " "));
    }

    /**
     * Tries to obtain an enum value assuming the given text is a default translation of the enum name.
     *
     * @param type must not be {@literal null}.
     * @param text must not be {@literal null} or empty.
     * @return
     */
    private <T extends Enum<?>> T fromDefault(Class<T> type, String text) {
        return resolveEnum(type, text.toUpperCase(Locale.US).replaceAll(" ", "_"), true);
    }
}